<?php

/**
 * @file 分析所有模块在 hook_cronjobs 中定义的任务当前是否需要执行，若需要，则写入 cron 表
 */

_cron_jobs();

// protected _cron_jobs() {{{ 
/**
 * 加载所有模块的 cronjobs.inc 文件，触发 hook_cronjobs()
 * _cron_jobs
 * 
 * @access protected
 * @return void
 */
function _cron_jobs() {
  module_include_file('cronjobs.inc');

  $cronjobs = _module_invoke_all('cronjobs', 0);

  // 没有定义任何任务
  if (empty($cronjobs)) return false;

  $year = date('Y');
  $month = date('n');
  $day = date('j');
  $hour = date('G');
  $minute = date('i');
  
  $visit = $counts = 0;

  $date_tag = date('ymdHi');

  $query = 'INSERT INTO {cron} SET type = :type, date = :date, created = :created, 
    status = :status, weight = :weight, data = :data';

  $old_data = array();
  // 在 cron_history 正常执行的情况下，cron 数据量较小，读取全表比使用 group by 效率更高
  if ($fetch = db_query('SELECT * FROM {cron} ORDER BY cid DESC')) {
    foreach ($fetch as $o) {
      if (empty($old_data[$o->type])) {
        $o->data = unserialize($o->data);
        $old_data[$o->type] = $o;
      }
    }
  }

  foreach ($cronjobs as $module => $jobs) {
    foreach ($jobs as $key => $data) {
      $visit++;

      $value = array(
        ':type' => md5($module . $key),
        ':date' => $date_tag,
        ':created' => time(),
        ':status' => 0,
        ':weight' => !empty($data['weight']) ? $data['weight'] : 0,
        ':data' => array(
          'module' => $module,
          'type' => $key
        )
      );

      // 默认检查任务运行状态
      if (empty($data['check_status'])) $data['check_status'] = 'ok';

      $last_data = NULL; 

      if (!empty($old_data[$value[':type']])) {
        $last_data = $old_data[$value[':type']];
        if ($last_data->date == $value[':date']) {
          message(t('system', '当前分钟内已有相同任务'));
          continue;
        }
      }

      // 检查任务运行状态
      if ($data['check_status'] == 'ok') {
        // 上一次执行还没有结束
        if (!empty($last_data) && $last_data->status < 2) {
          continue;
        }
      }

      /**
       *  最小时间，当时小时必须大于设定数字，才允许执行
       *  应用场景，如：设置某任务在每天夜里 11 点后，每隔 1 小时执行 1 次
       */
      if (!empty($data['minHour'])) {
        if ($hour < $data['minHour']) {
          continue;
        }
      }
      
      // 最大时间，与最小时间相反
      if (!empty($data['maxHour'])) {
        if ($hour > $data['maxHour']) {
          continue;
        }
      }

      // 计划任务时间格式与 linux 系统一致，可使用具体数字，如：2012，也可使用 *，还可以使用 */3
      // 注意：年月不允许使用 */3 这种格式
      if ($data['year'] != '*' && $data['year'] != $year) continue;
      if ($data['month'] != '*' && $data['month'] != $month) continue;

      if (empty($last_data)) {
        $last_data = new stdClass();
        $last_data->date = 0;
        $last_data->created = 0;
      }
     
      // 去掉 cron 设置中的前缀 0，例如，将 03 改为 3
      foreach (array('day', 'month', 'minute') as $_k) {
        if (is_numeric($data[$_k])) {
          $data[$_k] = intval($data[$_k]);
        }
      }

      // day 参数为 */3 的格式
      // hour 和 minute 只支持具体数值，不支持 * 或 */x。无论设置为什么，在 3 天内都只会执行一次
      if (strpos($data['day'], '/') !== false) {
        // 若从未执行 ，判断当前小时和分钟设置是否匹配，若匹配，则执行
        if (empty($last_data->date)) {
          if (!is_numeric($data['hour']) || $data['hour'] == $hour) {
            if (!is_numeric($data['minute']) || $data['minute'] == $minute) {
              $counts++;
              $value[':data']['#id'] = 101;
              $value[':data'] = serialize($value[':data']);
              if (!db_exec($query, $value)) {
                message($module . '-' . $key . ' 写入失败');
              }
            }
          }
          continue;
        }

        // 判断上次执行时间是否为指定日期之前，若是，需立即执行
        if ($last_data->created < strtotime('-' . preg_replace('/[^0-9]/', '', $data['day']) . ' day')) {
          $counts++;
          $value[':data']['#id'] = 102;
          $value[':data'] = serialize($value[':data']);
          if (!db_exec($query, $value)) {
            message($module . '-' . $key . ' 写入失败');
          }
        }
        continue;
      }
      
      // 不合法的参数。合法参数为三种：*、*/3、25 
      if (!is_numeric($data['day']) && $data['day'] != '*') {
        message($module . '-' . $key .  ' day 参数不合法');
        continue;
      }

      //  若 $data['day'] 为数字
      if (is_numeric($data['day'])) {
        // 设定日期小于当前日期，表明还未到执行时间
        if ($data['day'] < $day) {
          continue;
        }
        
        // 设定日期大于当前日期
        if ($data['day'] > $day) {
          // 判断上次执行是否为一个月内，若不是，则可能是宕机导致错过了任务执行
          if (empty($last_data->date) || $last_data->created < strtotime('-1 month')) {
            $counts++;
            $value[':data']['#id'] = 111;
            $value[':data'] = serialize($value[':data']);
            if (!db_exec($query, $value)) {
              message($module . '-' . $key . ' 写入失败');
            }
          }
          continue;
        }
      }

      /**
       *  day 参数为 * 格式或 day 等于当前日期，判断 hour 和 minute
       */
      
      // hour 参数为 */3 的格式
      // minute 只支持具体数值，不支持 * 或 */x。无论设置为什么，在 3 小时内都只会执行一次
      if (strpos($data['hour'], '/') !== false) {
        // 若从未执行 ，判断当前分钟设置是否匹配，若匹配，则执行
        if (empty($last_data->date)) {
          if (!is_numeric($data['minute']) || $data['minute'] == $hour) {
            $counts++;
            $value[':data']['#id'] = 201;
            $value[':data'] = serialize($value[':data']);
            if (!db_exec($query, $value)) {
              message($module . '-' . $key . ' 写入失败');
            }
          }
          continue;
        }

        // 判断上次执行时间是否为指定日期之前，若是，需立即执行
        if ($last_data->created < strtotime('-' . preg_replace('/[^0-9]/', '', $data['hour']) . ' hours')) {
          $counts++;
          $value[':data']['#id'] = 202;
          $value[':data'] = serialize($value[':data']);
          if (!db_exec($query, $value)) {
            message($module . '-' . $key . ' 写入失败');
          }
        }
        continue;
      }
      
      // 不合法的参数。合法参数为三种：*、*/3、25 
      if (!is_numeric($data['hour']) && $data['hour'] != '*') {
        message($module . '-' . $key .  ' hour 参数不合法');
        continue;
      }

      //  若 $data['hour'] 为数字
      if (is_numeric($data['hour'])) {
        // 设定日期小于当前日期，表明还未到执行时间
        if ($data['hour'] < $hour) {
          continue;
        }
        
        // 设定小时大于当前小时
        if ($data['hour'] > $hour) {
          // 判断上次执行是否为一天内，若不是，则可能是宕机导致错过了任务执行
          if (empty($last_data->date) || $last_data->created < strtotime('-1 day')) {
            $counts++;
            $value[':data']['#id'] = 203;
            $value[':data'] = serialize($value[':data']);
            if (!db_exec($query, $value)) {
              message($module . '-' . $key . ' 写入失败');
            }
          }
          continue;
        }
      }

      /**
       *  hour 参数为 * 格式或 hour 等于当前小时，判断 minute
       */
      
      // minute 参数为 */3 的格式
      if (strpos($data['minute'], '/') !== false) {
        // 若从未执行 ，或上次执行时间在指定分钟之前，则立即执行
        if ($last_data->created < (time()-preg_replace('/[^0-9]/', '', $data['minute'])*60)) {
          $counts++;
          $value[':data']['#id'] = 301;
          $value[':data'] = serialize($value[':data']);
          if (!db_exec($query, $value)) {
            message($module . '-' . $key . ' 写入失败');
          }
        }
        continue;
      }
      
      // 匹配当前分钟
      if ($data['minute'] == $minute || $data['minute'] == '*') {
        // 若从未执行 ，或上次执行时间在 1 分钟之前，则立即执行
        if ($last_data->date < date('ymdHi')) {
          $counts++;
          $value[':data']['#id'] = 302;
          $value[':data'] = serialize($value[':data']);
          if (!db_exec($query, $value)) {
            message($module . '-' . $key . ' 写入失败');
          }
        }
        continue;
      }
    }
  }

  $text = t('system', '共有 !sum 个任务，当前需运行 !count 个任务，耗时 !time 秒，服务器：!server', array(
    '!count' => $counts,
    '!sum' => $visit, 
    '!server' => php_uname('n'),
    '!time' => round(microtime(true)-DIDA_START_TIME, 4)
  ));

  dd_log('cron', $text);
  message($text);
}
// }}}

